import { isEqual, map, find, fromPairs, merge, isEmpty } from "lodash";
import React, { useState, useMemo, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import useQueryResultData from "@/lib/useQueryResultData";
import useImmutableCallback from "@/lib/hooks/useImmutableCallback";
import Filters, { FiltersType, filterData } from "@/components/Filters";
import { VisualizationType } from "@redash/viz/lib";
import { Renderer } from "@/components/visualizations/visualizationComponents";
import Visualization from "@/services/visualization"
import { Query as QueryService } from "@/services/query";

function combineFilters(localFilters, globalFilters) {
  // tiny optimization - to avoid unnecessary updates
  if (localFilters.length === 0 || globalFilters.length === 0) {
    return localFilters;
  }

  return map(localFilters, localFilter => {
    const globalFilter = find(globalFilters, f => f.name === localFilter.name);
    if (globalFilter) {
      return {
        ...localFilter,
        current: globalFilter.current,
      };
    }
    return localFilter;
  });
}

function areFiltersEqual(a, b) {
  if (a.length !== b.length) {
    return false;
  }

  a = fromPairs(map(a, item => [item.name, item]));
  b = fromPairs(map(b, item => [item.name, item]));

  return isEqual(a, b);
}

function ComposeRender({props, visualization, visualizations, options, filters, filteredData, handleFiltersChange, visexArr}) {
  return map(options["compose"], (current, index) => {
    let left,top,width,height = '100%';
    if (options["composesize"]) {
      const sizeArray = options["composesize"].split("\n").filter(v => v !== "");
      if (sizeArray.length > index) {
        let size = sizeArray[index];
        let styleArray = size.split(",").filter(v => v !== "");
        if (styleArray.length > 0) left = styleArray[0];
        if (styleArray.length > 1) top = styleArray[1];
        if (styleArray.length > 2) width = styleArray[2];
        if (styleArray.length > 3) height = styleArray[3];
      }
    }

    let vis = find(visualizations, item => item.id === parseInt(current));

    if (vis) {
      let optionsex = { ...vis.options };
      if (vis.type === "TABLE") {
        optionsex.paginationSize = props.context === "widget" ? "small" : "default";
      }

      return (
        <Renderer
          key={`visualization${current}`}
          style={{ position: 'absolute', left, top, width, height }}
          type={vis.type}
          options={optionsex}
          data={filteredData}
          visualizationName={vis.name}
          visualizationId={vis.id}
          queryId={visualization.query_id}
          refreshStartedAt={props.refreshStartedAt}
          scale={props.scale}
          scaleX={props.scaleX}
          visualizations={visualizations}
          onClickHandler={props.onClickHandler}
          onCheckHandler={props.onCheckHandler}
          onRefreshDashboardHandler={props.onRefreshDashboardHandler}
          onRefreshWidgetHandler={props.onRefreshWidgetHandler}
          onExpandDashboardHandler={props.onExpandDashboardHandler}
          addonBefore={<Filters filters={filters} onChange={handleFiltersChange} />}
        />
      )
    } else {
      let visex = find(visexArr, v=>v.id===parseInt(current));
      if (visex) {
        let optionsex = { ...visex.options };
        if (visex.type === "TABLE") {
          optionsex.paginationSize = props.context === "widget" ? "small" : "default";
        }

        let storageName =  "views_" + visex.query_id;
        let data = sessionStorage.getItem(storageName);

        if (data) {
          let filteredDataex = JSON.parse(data);
          return (
            <Renderer
              key={`visualization${current}`}
              style={{ position: 'absolute', left, top, width, height }}
              type={visex.type}
              options={optionsex}
              data={filteredDataex}
              visualizationName={visex.name}
              visualizationId={visex.id}
              queryId={visex.query_id}
              refreshStartedAt={props.refreshStartedAt}
              scale={props.scale}
              scaleX={props.scaleX}
              // visualizations={visex.query.visualizations}
              onClickHandler={props.onClickHandler}
              onCheckHandler={props.onCheckHandler}
              onRefreshDashboardHandler={props.onRefreshDashboardHandler}
              onRefreshWidgetHandler={props.onRefreshWidgetHandler}
              onExpandDashboardHandler={props.onExpandDashboardHandler}
              // addonBefore={<Filters filters={filters} onChange={handleFiltersChange} />}
            />
          )
        }
      }
    }
  })      
}

export default function VisualizationRenderer(props) {
  const data = useQueryResultData(props.queryResult);
  const [filters, setFilters] = useState(() => combineFilters(data.filters, props.filters)); // lazy initialization
  const [visexArr, setVisexArr] = useState([]); 
  const [isLoaded, setIsLoaded] = useState(false); 
  const { visualization, visualizations, widget } = props;
  let options = { ...visualization.options };
  const filtersRef = useRef();
  filtersRef.current = filters;

  const handleFiltersChange = useImmutableCallback(newFilters => {
    if (!areFiltersEqual(newFilters, filters)) {
      setFilters(newFilters);
      props.onFiltersChange(newFilters);
    }
  });

  // Reset local filters when query results updated
  useEffect(() => {
    handleFiltersChange(combineFilters(data.filters, props.filters));
  }, [data.filters, props.filters, handleFiltersChange]);

  // Update local filters when global filters changed.
  // For correct behavior need to watch only `props.filters` here,
  // therefore using ref to access current local filters
  useEffect(() => {
    handleFiltersChange(combineFilters(filtersRef.current, props.filters));
  }, [props.filters, handleFiltersChange]);

  useEffect(() => {
    var lastIndex = -1;
    map(options["compose"], (current, index) => {
      let vis = find(visualizations, item => item.id === parseInt(current));
      if (!vis) lastIndex = index;
    });

    map(options["compose"], (current, index) => {
      let vis = find(visualizations, item => item.id === parseInt(current));
      if (vis) {
        if (lastIndex===-1 && index === options["compose"].length-1) setIsLoaded(true);
      } else {
        Visualization.get(parseInt(current)).then(visex => {
          if (find(visexArr, v => v.id === visex.id)) {
            if (index === lastIndex) setIsLoaded(true);
          } else {
            setVisexArr([...visexArr, visex]);

            let storageName =  "views_" + visex.query_id;
            let data = sessionStorage.getItem(storageName);

            if (data) { 
              if (index === lastIndex) setIsLoaded(true);
            } else {
              new QueryService(visex.query).getQueryResultPromise().then(queryResult => {
                let f = {
                  columns: queryResult.query_result.data.columns,
                  rows: filterData(queryResult.query_result.data.rows, filters),
                };
                try {
                  sessionStorage.setItem(storageName, JSON.stringify(f));
                  if (index === lastIndex) setIsLoaded(true);
                } catch(e) {
                } 
              });
            }
          }
        });
      }
    })
  }, [options, visualizations, filters, visexArr, isLoaded]);

  const filteredData = useMemo(
    () => ({
      columns: data.columns,
      rows: filterData(data.rows, filters),
    }),
    [data, filters]
  );

  // define pagination size based on context for Table visualization
  if (visualization.type === "TABLE") {
    options.paginationSize = props.context === "widget" ? "small" : "default";
  }

  var visstyle = {};
  if (!!props.height) visstyle = merge(visstyle, {height: props.height});
  if (!!props.width) visstyle = merge(visstyle, {width: props.width});

  return (!options["composewidget"] && !!widget) ? (
    <div 
      className={!!widget.getWidgetOptions("widget_noscroll") ? "body-row-auto noscrollbox": "body-row-auto scrollbox"}
      onClick={!!widget.getWidgetOptions("pop_widget") && props.onWidgetClick}
    >
    {!(options["composehide"] || false) && (
    <Renderer
      key={`visualization${visualization.id}`}
        style={isEmpty(visstyle) ? null : visstyle}
      type={visualization.type}
      options={options}
      data={filteredData}
      visualizationName={visualization.name}
        visualizationId={visualization.id}
        queryId={visualization.query_id}
        refreshStartedAt={props.refreshStartedAt}
        scale={props.scale}
        scaleX={props.scaleX}
        visualizations={visualizations}
        onClickHandler={props.onClickHandler}
        onCheckHandler={props.onCheckHandler}
        onRefreshDashboardHandler={props.onRefreshDashboardHandler}
        onRefreshWidgetHandler={props.onRefreshWidgetHandler}
        onExpandDashboardHandler={props.onExpandDashboardHandler}
        addonBefore={<Filters filters={filters} onChange={handleFiltersChange} />}
      />
    )}
    {isLoaded && (<ComposeRender props={props} 
      visualization={visualization} 
      visualizations={visualizations} 
      options={options} 
      filters={filters} 
      filteredData={filteredData}
      handleFiltersChange={handleFiltersChange}
      visexArr={visexArr}
    />)}
    </div>
  ) : (
    <>
    {!(options["composehide"] || false) && !!widget && (
      <div 
        className={!!widget.getWidgetOptions("widget_noscroll") ? "body-row-auto noscrollbox": "body-row-auto scrollbox"}
        onClick={!!widget.getWidgetOptions("pop_widget") && props.onWidgetClick}
      >
      <Renderer
        key={`visualization${visualization.id}`}
        style={isEmpty(visstyle) ? null : visstyle}
        type={visualization.type}
        options={options}
        data={filteredData}
        visualizationName={visualization.name}
        visualizationId={visualization.id}
        queryId={visualization.query_id}
        refreshStartedAt={props.refreshStartedAt}
        scale={props.scale}
        scaleX={props.scaleX}
        visualizations={visualizations}
        onClickHandler={props.onClickHandler}
        onCheckHandler={props.onCheckHandler}
        onRefreshDashboardHandler={props.onRefreshDashboardHandler}
        onRefreshWidgetHandler={props.onRefreshWidgetHandler}
        onExpandDashboardHandler={props.onExpandDashboardHandler}
        addonBefore={<Filters filters={filters} onChange={handleFiltersChange} />}
    />
      </div>
    )}
    {!(options["composehide"] || false) && !widget && (
      <Renderer
        key={`visualization${visualization.id}`}
        style={isEmpty(visstyle) ? null : visstyle}
        type={visualization.type}
        options={options}
        data={filteredData}
        visualizationName={visualization.name}
        visualizationId={visualization.id}
        queryId={visualization.query_id}
        refreshStartedAt={props.refreshStartedAt}
        scale={props.scale}
        scaleX={props.scaleX}
        visualizations={visualizations}
        onClickHandler={props.onClickHandler}
        onCheckHandler={props.onCheckHandler}
        onRefreshDashboardHandler={props.onRefreshDashboardHandler}
        onRefreshWidgetHandler={props.onRefreshWidgetHandler}
        onExpandDashboardHandler={props.onExpandDashboardHandler}
        addonBefore={<Filters filters={filters} onChange={handleFiltersChange} />}
      />
    )}
    {isLoaded && (<ComposeRender props={props} 
      visualization={visualization} 
      visualizations={visualizations} 
      options={options} 
      filters={filters} 
      filteredData={filteredData}
      handleFiltersChange={handleFiltersChange}
      visexArr={visexArr}
    />)}
    </>
  );
}

VisualizationRenderer.propTypes = {
  visualizations: PropTypes.arrayOf(PropTypes.object),
  visualization: VisualizationType.isRequired,
  queryResult: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  showFilters: PropTypes.bool,
  filters: FiltersType,
  onFiltersChange: PropTypes.func,
  queryId: PropTypes.number,
  refreshStartedAt: PropTypes.object,   // eslint-disable-line react/forbid-prop-types
  scale: PropTypes.number,
  scaleX: PropTypes.number,
  onClickHandler: PropTypes.func,
  onCheckHandler: PropTypes.func,
  onRefreshDashboardHandler: PropTypes.func,
  onRefreshWidgetHandler: PropTypes.func,
  onExpandDashboardHandler: PropTypes.func,
  context: PropTypes.oneOf(["query", "widget"]).isRequired,
  height: PropTypes.string,
  width: PropTypes.string,
  onWidgetClick: PropTypes.func,
};

VisualizationRenderer.defaultProps = {
  showFilters: true,
  filters: [],
  onFiltersChange: () => {},
  queryId: 0,
  refreshStartedAt: null,
  scale: 1,
  scaleX: 1,
  onClickHandler: (column, data) => {},
  onCheckHandler: (selectedRowKeys) => {},
  onRefreshDashboardHandler: () => {},
  onRefreshWidgetHandler: () => {},
  onExpandDashboardHandler: () => {},
  height: null,
  width: null,
};
